package com.alibaba.excel.write.executor;

import com.alibaba.excel.context.WriteContext;
import com.alibaba.excel.enums.HeadKindEnum;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.metadata.property.ExcelContentProperty;
import com.alibaba.excel.util.*;
import com.alibaba.excel.write.handler.context.CellWriteHandlerContext;
import com.alibaba.excel.write.handler.context.RowWriteHandlerContext;
import com.alibaba.excel.write.metadata.CollectionRowData;
import com.alibaba.excel.write.metadata.MapRowData;
import com.alibaba.excel.write.metadata.RowData;
import com.alibaba.excel.write.metadata.holder.WriteHolder;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.springframework.cglib.beans.BeanMap;

import java.lang.reflect.Field;
import java.util.*;

/**
 * 将数据添加到excel中
 *
 * @author Jiaju Zhuang
 */
public class ExcelWriteAddExecutor extends AbstractExcelWriteExecutor {

    public ExcelWriteAddExecutor(WriteContext writeContext) {
        super(writeContext);
    }

    /**
     * 写入数据到 Sheet
     * 获取头部下面的行，开始写入数据
     */
    public void add(Collection<?> data) {
        if (CollectionUtils.isEmpty(data)) {
            data = new ArrayList<>();
        }

        int newRowIndex = getNewRowIndex();

        // BeanMap 乱序，所以使用 sortedAllFieldMap
        Map<Integer, Field> sortedAllFieldMap = new TreeMap<>();

        // 相对行索引
        int relativeRowIndex = 0;

        // 遍历数据
        for (Object oneRowData : data) {
            // 从最后一行索引开始写
            int lastRowIndex = relativeRowIndex + newRowIndex;
            // 将输入写入行
            addOneRowOfDataToExcel(oneRowData, lastRowIndex, relativeRowIndex, sortedAllFieldMap);
            // 行数添加
            relativeRowIndex++;
        }
    }

    private int getNewRowIndex() {
        // （写入表持有者）当前表的所有信息
        WriteSheetHolder writeSheetHolder = writeContext.writeSheetHolder();

        int newRowIndex = getNewRowIndex(writeSheetHolder);
        return newRowIndex;
    }

    private int getNewRowIndex(WriteSheetHolder writeSheetHolder) {
        // 获取索引的最后一行，你要确保接下来写入的数据
        int newRowIndex = writeSheetHolder.getNewRowIndexAndStartDoWrite();

        // 如果 writeSheetHolder 是新的，并且拥有头部，那么从头部下面的行开始写
        if (writeSheetHolder.isNew() && !writeSheetHolder.getExcelWriteHeadProperty().hasHead()) {
            newRowIndex += writeContext.currentWriteHolder().relativeHeadRowIndex();
        }
        return newRowIndex;
    }

    /**
     * 将数据写入 Excel 的行
     *
     *
     * @param oneRowData 数据
     * @param rowIndex 行下标
     * @param relativeRowIndex 相对行下标
     * @param sortedAllFieldMap
     */
    private void addOneRowOfDataToExcel(Object oneRowData, int rowIndex, int relativeRowIndex, Map<Integer, Field> sortedAllFieldMap) {
        if (oneRowData == null) {
            return;
        }

        // 行写入处理器上下文
        RowWriteHandlerContext rowWriteHandlerContext = WriteHandlerUtils.createRowWriteHandlerContext(writeContext, rowIndex, relativeRowIndex, Boolean.FALSE);
        // 行创建前
        WriteHandlerUtils.beforeRowCreate(rowWriteHandlerContext);

        // 【创建行】
        Row row = WorkBookUtil.createRow(writeContext.writeSheetHolder().getSheet(), rowIndex);
        rowWriteHandlerContext.setRow(row);

        // 创建行后
        WriteHandlerUtils.afterRowCreate(rowWriteHandlerContext);

        // 写入数据
        // 如果数据是集合
        if (oneRowData instanceof Collection<?>) {
            addBasicTypeToExcel(new CollectionRowData((Collection<?>)oneRowData), row, rowIndex, relativeRowIndex);
        }
        // 如果数据是 Map
        else if (oneRowData instanceof Map) {
            addBasicTypeToExcel(new MapRowData((Map<Integer, ?>)oneRowData), row, rowIndex, relativeRowIndex);
        } else {
            // 如果数据是对象
            addJavaObjectToExcel(oneRowData, row, rowIndex, relativeRowIndex, sortedAllFieldMap);
        }

        WriteHandlerUtils.afterRowDispose(rowWriteHandlerContext);
    }

    /**
     * 将基本数据类型添加到 Excel
     * @param oneRowData
     * @param row
     * @param rowIndex
     * @param relativeRowIndex
     */
    private void addBasicTypeToExcel(RowData oneRowData, Row row, int rowIndex, int relativeRowIndex) {
        if (oneRowData.isEmpty()) return;

        // 获取 Excel 头
        // 从上下文中获取 “写持有者”，再获取 “Excel头属性”，再获取 “Excel头Map”
        Map<Integer, Head> headMap = writeContext.currentWriteHolder().excelWriteHeadProperty().getHeadMap();
        int dataIndex = 0;
        int maxCellIndex = -1;

        // 遍历头部
        for (Map.Entry<Integer, Head> entry : headMap.entrySet()) {
            // 当头部下标超过数据数量，跳过（dataIndex 下标 >= 数据大小）
            if (dataIndex >= oneRowData.size()) return;

            // 头部下标
            int columnIndex = entry.getKey();
            // 头部数据
            Head head = entry.getValue();
            // 指定添加
            doAddBasicTypeToExcel(oneRowData, head, row, rowIndex, relativeRowIndex, dataIndex++, columnIndex);
            maxCellIndex = Math.max(maxCellIndex, columnIndex);
        }
        // Finish
        if (dataIndex >= oneRowData.size()) {
            return;
        }
        // fix https://github.com/alibaba/easyexcel/issues/1702
        // If there is data, it is written to the next cell
        maxCellIndex++;

        int size = oneRowData.size() - dataIndex;
        for (int i = 0; i < size; i++) {
            doAddBasicTypeToExcel(oneRowData, null, row, rowIndex, relativeRowIndex, dataIndex++, maxCellIndex++);
        }
    }

    /**
     * 将基本类型添加到 Excel
     * @param oneRowData
     * @param head
     * @param row
     * @param rowIndex
     * @param relativeRowIndex
     * @param dataIndex
     * @param columnIndex
     */
    private void doAddBasicTypeToExcel(RowData oneRowData, Head head, Row row, int rowIndex, int relativeRowIndex,
        int dataIndex, int columnIndex) {

        // 获取内容属性（字段、数据格式、字体、内容风格等）
        ExcelContentProperty excelContentProperty = ClassUtils.declaredExcelContentProperty(null,
            writeContext.currentWriteHolder().excelWriteHeadProperty().getHeadClazz(),
            head == null ? null : head.getFieldName());

        // 单元格上下文
        CellWriteHandlerContext cellWriteHandlerContext = WriteHandlerUtils.createCellWriteHandlerContext(writeContext,
            row, rowIndex, head, columnIndex, relativeRowIndex, Boolean.FALSE, excelContentProperty);
        // 单元格创建前
        WriteHandlerUtils.beforeCellCreate(cellWriteHandlerContext);

        // 创建单元格
        Cell cell = WorkBookUtil.createCell(row, columnIndex);
        cellWriteHandlerContext.setCell(cell);

        // 单元格创建后
        WriteHandlerUtils.afterCellCreate(cellWriteHandlerContext);

        // 设置原始值
        cellWriteHandlerContext.setOriginalValue(oneRowData.get(dataIndex));
        // 设置原始字段类
        cellWriteHandlerContext.setOriginalFieldClass(FieldUtils.getFieldClass(cellWriteHandlerContext.getOriginalValue()));
        // 转换和设置值
        converterAndSet(cellWriteHandlerContext);

        WriteHandlerUtils.afterCellDispose(cellWriteHandlerContext);
    }

    private void addJavaObjectToExcel(Object oneRowData, Row row, int rowIndex, int relativeRowIndex, Map<Integer, Field> sortedAllFieldMap) {
        WriteHolder currentWriteHolder = writeContext.currentWriteHolder();

        // 将 Java 对象解析为 BeanMap
        BeanMap beanMap = BeanMapUtils.create(oneRowData);
        // bean 的包含性能较差的 Map Key 方法，所以在这里创建一个 keySet
        Set<String> beanKeySet = new HashSet<>(beanMap.keySet());
        Set<String> beanMapHandledSet = new HashSet<>();
        int maxCellIndex = -1;

        // 如果它是一个类，则需要按类型强制转换
        if (HeadKindEnum.CLASS.equals(writeContext.currentWriteHolder().excelWriteHeadProperty().getHeadKind())) {
            Map<Integer, Head> headMap = writeContext.currentWriteHolder().excelWriteHeadProperty().getHeadMap();
            for (Map.Entry<Integer, Head> entry : headMap.entrySet()) {
                int columnIndex = entry.getKey();
                Head head = entry.getValue();
                String name = head.getFieldName();
                if (!beanKeySet.contains(name)) {
                    continue;
                }

                ExcelContentProperty excelContentProperty = ClassUtils.declaredExcelContentProperty(beanMap,
                    currentWriteHolder.excelWriteHeadProperty().getHeadClazz(), name);
                // 创建列写入处理器（为什么不直接写入 Cell，因为要对 Cell 进行一些处理，所以用 CellWriteHandlerContext 包装 Cell，保存一些信息用于处理 Cell）
                CellWriteHandlerContext cellWriteHandlerContext = WriteHandlerUtils.createCellWriteHandlerContext(
                    writeContext, row, rowIndex, head, columnIndex, relativeRowIndex, Boolean.FALSE,
                    excelContentProperty);

                // 创建列前置处理器
                WriteHandlerUtils.beforeCellCreate(cellWriteHandlerContext);
                // 创建一个列
                Cell cell = WorkBookUtil.createCell(row, columnIndex);
                cellWriteHandlerContext.setCell(cell);
                // 创建列后置处理器
                WriteHandlerUtils.afterCellCreate(cellWriteHandlerContext);

                // 给列写入数据
                // 这里先将数据写入到 Cell上下文
                cellWriteHandlerContext.setOriginalValue(beanMap.get(name)); // 设置字段值
                cellWriteHandlerContext.setOriginalFieldClass(head.getField().getType()); // 设置字段的 Java 类型
                // 这里才是给 Cell 写入数据
                converterAndSet(cellWriteHandlerContext);

                // Cell 后置处理
                WriteHandlerUtils.afterCellDispose(cellWriteHandlerContext);

                beanMapHandledSet.add(name);
                maxCellIndex = Math.max(maxCellIndex, columnIndex);
            }
        }
        // Finish
        if (beanMapHandledSet.size() == beanMap.size()) {
            return;
        }
        maxCellIndex++;

        Map<String, Field> ignoreMap = writeContext.currentWriteHolder().excelWriteHeadProperty().getIgnoreMap();
        initSortedAllFieldMapFieldList(oneRowData.getClass(), sortedAllFieldMap);
        for (Map.Entry<Integer, Field> entry : sortedAllFieldMap.entrySet()) {
            Field field = entry.getValue();
            String fieldName = FieldUtils.resolveCglibFieldName(field);
            boolean uselessData = !beanKeySet.contains(fieldName) || beanMapHandledSet.contains(fieldName)
                || ignoreMap.containsKey(fieldName);
            if (uselessData) {
                continue;
            }
            Object value = beanMap.get(fieldName);
            ExcelContentProperty excelContentProperty = ClassUtils.declaredExcelContentProperty(beanMap,
                currentWriteHolder.excelWriteHeadProperty().getHeadClazz(), fieldName);
            CellWriteHandlerContext cellWriteHandlerContext = WriteHandlerUtils.createCellWriteHandlerContext(
                writeContext, row, rowIndex, null, maxCellIndex, relativeRowIndex, Boolean.FALSE, excelContentProperty);
            WriteHandlerUtils.beforeCellCreate(cellWriteHandlerContext);

            // fix https://github.com/alibaba/easyexcel/issues/1870
            // If there is data, it is written to the next cell
            Cell cell = WorkBookUtil.createCell(row, maxCellIndex);
            cellWriteHandlerContext.setCell(cell);

            WriteHandlerUtils.afterCellCreate(cellWriteHandlerContext);

            cellWriteHandlerContext.setOriginalValue(value);
            cellWriteHandlerContext.setOriginalFieldClass(FieldUtils.getFieldClass(beanMap, fieldName, value));
            converterAndSet(cellWriteHandlerContext);

            WriteHandlerUtils.afterCellDispose(cellWriteHandlerContext);
            maxCellIndex++;
        }
    }

    private void initSortedAllFieldMapFieldList(Class<?> clazz, Map<Integer, Field> sortedAllFieldMap) {
        if (!sortedAllFieldMap.isEmpty()) {
            return;
        }

        WriteSheetHolder writeSheetHolder = writeContext.writeSheetHolder();
        boolean needIgnore =
            !CollectionUtils.isEmpty(writeSheetHolder.getExcludeColumnFieldNames()) || !CollectionUtils
                .isEmpty(writeSheetHolder.getExcludeColumnIndexes()) || !CollectionUtils
                .isEmpty(writeSheetHolder.getIncludeColumnFieldNames()) || !CollectionUtils
                .isEmpty(writeSheetHolder.getIncludeColumnIndexes());
        ClassUtils.declaredFields(clazz, sortedAllFieldMap, needIgnore, writeSheetHolder);
    }

}
